package webx

import (
	"encoding/json"
	"io"
	"log/slog"
	"net/url"
	"reflect"
	"strings"
	"time"

	"gitee.com/xungen/goweb/errno"
	"gitee.com/xungen/goweb/utils"
)

func getHttpStatus(code int) int {
	switch code {
	case errno.TIMEOUT.Code():
		return 401
	case errno.SYSBUSY.Code():
		return 503
	case errno.DAYLIMIT.Code():
		return 503
	case errno.PARAMERR.Code():
		return 400
	case errno.AUTHFAIL.Code():
		return 403
	default:
		return 500
	}
}

func readAll(file io.Reader, buffer []byte) (int, error) {
	return utils.ReadAll(file, buffer, 30*time.Second)
}

func writeAll(file io.Writer, buffer []byte) (int, error) {
	return utils.WriteAll(file, buffer, 30*time.Second)
}

func process(ctx *contextImpl, doc *document, proc any) {
	var data string
	var params map[string]string
	const maxsz = 8 * 1024 * 1024

	defer func() {
		if err := recover(); err != nil {
			slog.ErrorContext(ctx, "process error", errno.Attr(err))
			ctx.GetHttpRequest().Body.Close()
		}
	}()

	if sum := int(ctx.GetHttpRequest().ContentLength); sum > 0 {
		if sum > maxsz {
			errno.DATAERR.Panic("request message too long")
		}
		buff := make([]byte, sum)
		if errno.AssertNoError(readAll(ctx.GetHttpRequest().Body, buff)) < sum {
			errno.NETERR.Panic()
		}
		if end := sum - 1; end > 0 && buff[0] == '{' && buff[end] == '}' {
			data = string(buff)
			json.Unmarshal(buff, ctx.GetRequest())
			params = utils.GetSimpleFields(ctx.GetRequest())
		} else {
			data = string(buff)
		}
	} else {
		data = ctx.GetHttpRequest().URL.RawQuery
	}

	if len(data) > 0 {
		if params == nil {
			data = strings.ReplaceAll(data, "+", "%2B")
			if data, err := url.ParseQuery(data); err == nil {
				params = setFieldValues(ctx.GetRequest(), data)
			}
		}
	}

	slog.DebugContext(ctx, "process begin", slog.Any("request", data))
	data = string(getResult(ctx, doc, proc, params))
	slog.DebugContext(ctx, "process end", slog.Any("response", data))
}

func setFieldValues(dest any, src url.Values) map[string]string {
	resmap := make(map[string]string)
	if dest == nil {
		return resmap
	}
	elem := reflect.ValueOf(dest).Elem()
	idxmap := utils.GetFieldIndexMap(elem.Type())
	if len(idxmap) == 0 {
		return resmap
	}
	for k, v := range src {
		if len(v) == 0 {
			continue
		}
		if idx, ok := idxmap[k]; ok {
			utils.SetFieldValue(elem.Field(idx), v[0])
			resmap[k] = v[0]
		} else {
			k = utils.CamelCase(k)
			if idx, ok = idxmap[k]; ok {
				utils.SetFieldValue(elem.Field(idx), v[0])
				resmap[k] = v[0]
			}
		}
	}
	return resmap
}

func getResult(ctx *contextImpl, doc *document, proc any, params map[string]string) (result []byte) {
	var writer = ctx.GetHttpResponseWriter()
	defer func() {
		if err := recover(); err != nil {
			if except, ok := err.(errno.Exception); ok {
				if except.Code() == errno.NOTCHANGED.Code() {
					etag := ctx.GetHttpRequest().Header.Get("If-None-Match")
					if len(etag) > 0 {
						writer.Header().Set("ETag", etag)
						writer.WriteHeader(304)
						result = []byte{}
					}
				}
				if result == nil {
					result = except.JsonBuffer()
					writer.WriteHeader(getHttpStatus(except.Code()))
					slog.ErrorContext(ctx, "process failed", errno.Attr(except))
				}
			} else {
				slog.ErrorContext(ctx, "process panic", errno.Attr(err))
				result = errno.SYSERR.JsonBuffer()
				writer.WriteHeader(500)
			}
		}
		if len(result) > 0 {
			errno.AssertNoError(writeAll(writer, result))
		}
	}()

	if num := len(doc.fieldList); num > 0 {
		if params == nil {
			doc.fieldList[0].checkValue("")
		} else {
			for _, v := range doc.fieldList {
				v.checkValue(params[v.name])
			}
		}
	}
	vals := reflect.ValueOf(proc).Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(ctx.GetRequest()), reflect.ValueOf(ctx.GetResponse())})
	if num := len(vals); num > 0 {
		if err, ok := vals[num-1].Interface().(error); ok {
			errno.AssertNil(err)
		}
	}
	result = errno.AssertNoError(json.Marshal(ctx.GetResponse()))
	return result
}
